package com.ticket.sass.common.ticketserver.platform.fhyz

import com.google.gson.Gson
import com.ticket.sass.common.ticketserver.*
import com.ticket.sass.common.ticketserver.configure.ConfigData
import com.ticket.sass.common.ticketserver.dto.input.*
import com.ticket.sass.common.ticketserver.dto.output.*
import com.ticket.sass.common.ticketserver.enums.OrderTicketStatus
import com.ticket.sass.common.ticketserver.enums.SeatStatus
import com.ticket.sass.common.ticketserver.exceptions.ApiStatusException
import com.ticket.sass.common.ticketserver.exceptions.ScheduleSeatMapException
import com.ticket.sass.common.ticketserver.platform.fhyz.response.*
import org.springframework.stereotype.Component
import java.time.LocalDateTime
import java.time.ZoneOffset


/**
 * 凤凰云智
 *
 * @author 朱其鹏
 * @date 2024/03/20 11:55
 */
@Component("ticketserver.fhyz.ApiInterfaceImpl")
class ApiInterfaceImpl : ApiInterface() {
    /**
     * 配置数据
     */
    val configData
        get() = config.data as ConfigData.Fhyz

    override fun schedules(input: ScheduleInput): ScheduleOutput {
        val params = listOf(
            mapOf("api" to "ykse.lark.partner.schedule.getSchedules"),
            mapOf("v" to "1.0"),
            mapOf("channelCode" to configData.channelCode),
            mapOf("timestamp" to LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli().toString()),
            mapOf("data" to Gson().toJson(mapOf(
                "cinemaLinkId" to configData.cinemaLinkId,
                "startDate" to input.startDate,
                "endDate" to input.endDate
            ))),
        )

        val res = RequestClient.send(params, Configurer.generateTicketSign(configData.secretKey, params))
            .parseJson(ScheduleModel::class.java)
            .also { it.checkApiStatus() }

        return ScheduleOutput(
            res.data.dataList.map {
                val startTime = it.showDateTime
                val endTime = startTime.toLocalDateTime("yyyy-MM-dd HH:mm:ss")
                    .plusMinutes(it.film.duration.toLong())
                    .format("yyyy-MM-dd HH:mm:ss")

                ScheduleOutput.Schedule(
                    it.scheduleId,
                    it.scheduleKey,
                    startTime,
                    endTime,
                    it.saleStatus,
                    it.film.name,
                    it.film.filmCode,
                    it.film.dimensional,
                    it.hallName,
                    it.hallCode,
                    0f,
                    it.standardPrice.toFloat(),
                    it.lowestPrice.toFloat(),
                    if (it.ticketFee.isNotBlank()) it.ticketFee.toFloat() else 0f,
                    it.film.language,
                    it.seatVersion,
                )
            }
        )
    }

    override fun scheduleSeatMap(input: ScheduleSeatMapInput): ScheduleSeatMapOutput {
        val seats = hallSeatMap(HallSeatMapInput(input.hallCode))
        val seatPlan = seats.data.data.seatPlanList.find {
            it.seatVersion == input.seatVersion
        } ?: throw ScheduleSeatMapException("1", "座位图获取版本匹配失败，版本：${input.seatVersion}")

        val params = listOf(
            mapOf("api" to "ykse.lark.partner.seat.getScheduleSoldSeats"),
            mapOf("v" to "1.0"),
            mapOf("channelCode" to configData.channelCode),
            mapOf("timestamp" to LocalDateTime.now().toTimestamp().toString()),
            mapOf("data" to Gson().toJson(mapOf(
                "cinemaLinkId" to configData.cinemaLinkId,
                "scheduleId" to input.sessionCode,
                "scheduleKey" to input.scheduleKey,
                "sectionCode" to seatPlan.sectionList.first().sectionCode,
            )))
        )

        val maxRow = seatPlan.sectionList.first().seatList.maxOfOrNull { it.y.toInt() } ?: 0
        val maxCol = seatPlan.sectionList.first().seatList.maxOfOrNull { it.x.toInt() } ?: 0

        val seatMap: MutableMap<String, HallSeatMapModel.Data.SeatInfo.SeatPlan.Section.Seat> = mutableMapOf()
        seatPlan.sectionList.first().seatList.forEach {
            seatMap["${it.y}-${it.x}"] = it
        }

        val res = RequestClient.send(params, Configurer.generateTicketSign(configData.secretKey, params))
            .parseJson(ScheduleSeatMapModel::class.java)
            .also { it.checkApiStatus() }

        val soldSeatMap: MutableMap<String, ScheduleSeatMapModel.Data.SeatInfo.Section.Seat> = mutableMapOf()
        if (res.data.data.sectionList.isNotEmpty()) {
            res.data.data.sectionList.first().seatList.forEach {
                soldSeatMap[it.seatCode] = it
            }
        }

        val seatData: MutableList<MutableList<ScheduleSeatMapOutput.Seat>> = mutableListOf()

        for (row in 1 .. maxRow) {
            val rows = mutableListOf<ScheduleSeatMapOutput.Seat>()
            seatData.add(rows)
            for (col in 1 .. maxCol) {
                val seat = ScheduleSeatMapOutput.Seat(
                    seatMap["$row-$col"]?.seatCode ?: "",
                    seatMap["$row-$col"]?.seatCode ?: "",
                    seatMap["$row-$col"]?.rowId ?: "",
                    seatMap["$row-$col"]?.columnId ?: "",
                    row,
                    col,
                    statusFormat(seatMap["$row-$col"], soldSeatMap[seatMap["$row-$col"]?.seatCode]),
                    0,
                )
                seatData[row - 1].add(seat)
            }
        }

        return ScheduleSeatMapOutput(seatData)
    }

    override fun halls(): HallOutput {
        val params = listOf(
            mapOf("api" to "ykse.lark.partner.cinema.getCinemas"),
            mapOf("v" to "1.0"),
            mapOf("channelCode" to configData.channelCode),
            mapOf("timestamp" to LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli().toString()),
        )

        val res = RequestClient.send(params, Configurer.generateTicketSign(configData.secretKey, params))
            .parseJson(HallModel::class.java)
            .also { it.checkApiStatus() }

        val cinema = res.data.dataList.firstOrNull { it.cinemaLinkId == configData.cinemaLinkId }
            ?: throw ApiStatusException("1", "影院信息不存在")

        return HallOutput(cinema.hallList.map {
            HallOutput.Hall(
                it.hallName,
                it.hallCode,
                it.hallType
            )
        })
    }

    override fun seatLock(input: SeatLockInput): SeatLockOutput {
        return SeatLockOutput("", "", 1)
    }

    override fun seatUnlock(input: SeatUnlockInput): SeatUnlockOutput {
        return SeatUnlockOutput(true)
    }

    override fun orderTicketSubmit(input: OrderTicketSubmitInput): OrderTicketSubmitOutput {
        return OrderTicketSubmitOutput(
            "abcdefg",
            listOf(
                OrderTicketSubmitOutput.TakeCode("取票号", "2015121972809795")
            ),
            listOf()
        )
    }

    override fun orderTicketStatus(input: OrderTicketStatusInput): OrderTicketStatusOutput {
        return OrderTicketStatusOutput(
            OrderTicketStatus.SUCCESS,
            listOf(
                OrderTicketStatusOutput.TakeCode("取票号", "2015121972809795")
            ),
            listOf()
        )
    }

    override fun orderTicketInfo(input: OrderTicketInfoInput): OrderTicketInfoOutput {
        val params = listOf(
            mapOf("api" to "ykse.lark.partner.order.getPrintInfo"),
            mapOf("v" to "1.0"),
            mapOf("channelCode" to configData.channelCode),
            mapOf("timestamp" to LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli().toString()),
            mapOf("data" to Gson().toJson(mapOf(
                "cinemaLinkId" to configData.cinemaLinkId,
                "printCode" to input.code,
            ))),
        )

        val res = RequestClient.send(params, Configurer.generateTicketSign(configData.secretKey, params))
            .parseJson(GetPrintInfoModel::class.java)
            .also { it.checkApiStatus() }

        val data = res.data.data

        return OrderTicketInfoOutput(
            data.cinemaName,
            data.showDateTime,
            data.hallCode,
            data.hallName,
            "",
            data.filmCode,
            data.shortName,
            data.ticketList.map {
                OrderTicketInfoOutput.Ticket(
                    it.infoCode,
                    it.ticketNo,
                    it.printFlag,
                    it.rowId,
                    it.columnId,
                    it.seatCode,
                    "${it.rowId}排${it.columnId}座",
                    it.ticketPrice,
                    it.serviceFee,
                )
            },
            channelCode = data.channelCode,
            channelName = data.channelName
        )
    }

    override fun orderTicketRefund(input: OrderTicketRefundInput): OrderTicketRefundOutput {
        return OrderTicketRefundOutput(true)
    }

    /**
     * 获取影厅座位图
     *
     * @param input HallSeatMapInput
     */
    private fun hallSeatMap(input: HallSeatMapInput): HallSeatMapModel {
        val params = listOf(
            mapOf("api" to "ykse.lark.partner.seat.getSeats"),
            mapOf("v" to "1.0"),
            mapOf("channelCode" to configData.channelCode),
            mapOf("timestamp" to LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli().toString()),
            mapOf("data" to Gson().toJson(mapOf(
                "cinemaLinkId" to configData.cinemaLinkId,
                "hallCode" to input.hallCode,
            )))
        )

        return RequestClient.send(params, Configurer.generateTicketSign(configData.secretKey, params))
            .parseJson(HallSeatMapModel::class.java)
            .also { it.checkApiStatus() }
    }

    /**
     * 检查接口响应数据状态
     */
    private fun BaseModel.checkApiStatus() {
        if (this.retCode != "0") throw ApiStatusException("1", this.retMsg)
        if (!this.data.success) throw ApiStatusException("1", this.data.bizMsg)
    }

    /**
     * 格式化座位状态
     *
     * @param seat 座位信息
     * @param soldSeat 座位状态信息
     *
     * @return 座位状态
     */
    private fun statusFormat(
        seat: HallSeatMapModel.Data.SeatInfo.SeatPlan.Section.Seat?,
        soldSeat: ScheduleSeatMapModel.Data.SeatInfo.Section.Seat?
    ): Short {
        if (seat == null) {
            return SeatStatus.BLANK.value
        }

        return when (soldSeat?.status) {
            "locked" -> SeatStatus.LOCKED.value
            "booked" -> SeatStatus.BOOKED.value
            "selled" -> SeatStatus.SOLD.value
            "damaged" -> SeatStatus.DAMAGED.value
            else -> SeatStatus.AVAILABLE.value
        }
    }
}
